using System;
using System.Collections.Generic;
using System.Linq;

namespace WindowsPhone.CompactContainer.ServiceLocation.CompactContainer
{
    /// <summary>
    /// Compact ontainer implementation
    /// </summary>
    public sealed class Container : IContainer
    {
        private readonly ComponentCollection _components = new ComponentCollection();
        private readonly Dictionary<Type, IHandler> _handlers = new Dictionary<Type, IHandler>();
        private int _singletonInstanceCount;
        private bool _disposed;
        
        /// <summary>
        /// Initializes a new instance of the <see cref="Container"/> class.
        /// </summary>
        public Container()
        {
            DefaultHandler = new DefaultHandler(this);
            AddComponentInstance("container", typeof(IContainer), this);
        }
        
        /// <summary>
        /// Gets the default handler.
        /// </summary>
        /// <value>The default handler.</value>
        public DefaultHandler DefaultHandler { get; set; }

        /// <summary>
        /// Gets the components.
        /// </summary>
        /// <value>The components.</value>
        public ComponentCollection Components
        {
            get { return _components; }
        }

        /// <summary>
        /// Gets the singletons instanciated count.
        /// </summary>
        /// <value>The singletons instanciated count.</value>
        public int SingletonInstanceCount
        {
            get { return _singletonInstanceCount; }
        }

        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="classType">Type of the class.</param>
        public void AddComponent(string key, Type classType)
        {
            AddComponent(key, classType, classType, LifestyleType.Singleton);
        }

        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="classType">Type of the class.</param>
        /// <param name="lifestyle">The lifestyle.</param>
        public void AddComponent(string key, Type classType, LifestyleType lifestyle)
        {
            AddComponent(key, classType, classType, lifestyle);
        }

        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="classType">Type of the class.</param>
        public void AddComponent(string key, Type serviceType, Type classType)
        {
            AddComponent(key, serviceType, classType, LifestyleType.Singleton);
        }

        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="classType">Type of the class.</param>
        /// <param name="lifestyle">The lifestyle.</param>
        public void AddComponent(string key, Type serviceType, Type classType, LifestyleType lifestyle)
        {
            if (HasComponent(key))
            {
                throw new ContainerException("key already registered: " + key);
            }

            ComponentInfo ci = new ComponentInfo(key, serviceType, classType, lifestyle);
            _components.Add(ci);
        }

        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <typeparam name="TClass">The type of the class.</typeparam>
        /// <param name="lifestyle">The lifestyle.</param>
        public void AddComponent<TService, TClass>(LifestyleType lifestyle) where TClass : TService
        {
            AddComponent(typeof(TService).FullName, typeof(TService), typeof(TClass), lifestyle);
        }


        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <typeparam name="TClass">The type of the class.</typeparam>
        /// <param name="key">The key.</param>
        /// <param name="lifestyle">The lifestyle.</param>
        public void AddComponent<TService, TClass>(string key, LifestyleType lifestyle) where TClass : TService
        {
            AddComponent(key, typeof(TService), typeof(TClass), lifestyle);
        }


        /// <summary>
        /// Adds the component.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <typeparam name="TClass">The type of the class.</typeparam>
        /// <param name="key">The key.</param>
        public void AddComponent<TService, TClass>(string key) where TClass : TService
        {
            AddComponent<TService, TClass>(key, LifestyleType.Singleton);
        }

        /// <summary>
        /// Removes the component.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        public void RemoveComponent<TService>()
        {
            var componentInfo = _components.FirstOrDefault(component => component.ServiceType == typeof (TService));

            if (componentInfo != null)
            {
                if (componentInfo.Lifestyle == LifestyleType.Singleton)
                    _singletonInstanceCount--;
                _components.Remove(componentInfo);
            }
        }

        /// <summary>
        /// Adds the component instance.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="instance">The instance.</param>
        public void AddComponentInstance<TService>(object instance)
        {
            AddComponentInstance(typeof(TService).Name, typeof(TService), instance);
        }

        /// <summary>
        /// Replaces the component instance.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="instance">The instance.</param>
        public void ReplaceComponentInstance<TService>(object instance)
        {
              var componentInfo = _components.FirstOrDefault(component => component.ServiceType == typeof (TService));

              if (componentInfo != null)
              {
                  var componenInfo = CreateComponenInfo(typeof (TService).Name, typeof (TService), instance);
                  _components[_components.IndexOf(componentInfo)] = componenInfo;
              }
            
        }

        /// <summary>
        /// Adds the component instance.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="instance">The instance.</param>
        public void AddComponentInstance(string key, object instance)
        {
            AddComponentInstance(key, instance.GetType(), instance);
        }

        /// <summary>
        /// Adds the component instance.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="instance">The instance.</param>
        public void AddComponentInstance(string key, Type serviceType, object instance)
        {
            if (HasComponent(key))
            {
                throw new ContainerException("key already registered");
            }

            ComponentInfo ci = CreateComponenInfo(key, serviceType, instance);

            _components.Add(ci);
            _singletonInstanceCount++;
        }

        private static ComponentInfo CreateComponenInfo(string key, Type serviceType, object instance)
        {
            ComponentInfo ci = new ComponentInfo(key, serviceType, instance.GetType(), LifestyleType.Singleton);
            ci.Instance = instance;
            return ci;
        }

        /// <summary>
        /// Determines whether the specified service has component.
        /// </summary>
        /// <param name="service">The service.</param>
        /// <returns>
        /// 	<c>true</c> if the specified service has component; otherwise, <c>false</c>.
        /// </returns>
        public bool HasComponent(Type service)
        {
            ComponentInfo find = GetComponentInfo(service);
            return find != null;
        }

        /// <summary>
        /// Determines whether the specified key has component.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if the specified key has component; otherwise, <c>false</c>.
        /// </returns>
        public bool HasComponent(string key)
        {
            ComponentInfo find = GetComponentInfo(key);
            return find != null;
        }

        /// <summary>
        /// Resolves the specified service.
        /// </summary>
        /// <param name="service">The service.</param>
        /// <returns></returns>
        public object Resolve(Type service)
        {
            ComponentInfo ci = GetComponentInfo(service);

            if (ci == null)
                throw new ContainerException(service.Name + " not registered"); 

            return ResolveComponent(ci);
        }

        /// <summary>
        /// Resolves the component for the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public object Resolve(string key)
        {
            ComponentInfo ci = GetComponentInfo(key);
            
            if (ci == null)
                throw new ContainerException("service with key: [" + key + "] not registered"); 

            return ResolveComponent(ci);
        }

        /// <summary>
        /// Resolves the component for the type
        /// </summary>
        /// <typeparam name="T">The type to resolve</typeparam>
        /// <returns></returns>
        public T Resolve<T>() where T : class
        {
            return (T)Resolve(typeof(T));
        }

        /// <summary>
        /// Resolves the component for the type and key
        /// </summary>
        /// <typeparam name="T">The type to resolve</typeparam>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public T Resolve<T>(string key) where T : class
        {
            return (T)Resolve(key);
        }

        /// <summary>
        /// Gets the services.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns></returns>
        public object[] GetServices(Type serviceType)
        {
            List<ComponentInfo> implementors = _components.GetComponentInfoListFor(serviceType);

            object[] services = new object[implementors.Count];

            int i = 0;
            implementors.ForEach(delegate(ComponentInfo ci)
                                     {
                                         services[i++] = ResolveComponent(ci);
                                     });

            return services;
        }

        /// <summary>
        /// Gets the services.
        /// </summary>
        /// <typeparam name="T">The types to find</typeparam>
        /// <returns></returns>
        public T[] GetServices<T>()
        {
            List<ComponentInfo> implementors = _components.GetComponentInfoListFor(typeof(T));

            T[] services = new T[implementors.Count];

            int i = 0;
            implementors.ForEach(delegate(ComponentInfo ci)
                                     {
                                         services[i++] = (T)ResolveComponent(ci);
                                     });

            return services;
        }

        /// <summary>
        /// Registers the handler.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="handler">The handler.</param>
        public void RegisterHandler(Type targetType, IHandler handler)
        {
            handler.Container = this;
            _handlers.Add(targetType, handler);
        }

        /// <summary>
        /// Registers the handler.
        /// </summary>
        /// <typeparam name="T">The type to register the handler against</typeparam>
        /// <param name="handler">The handler.</param>
        public void RegisterHandler<T>(IHandler handler)
        {
            RegisterHandler(typeof(T), handler);
        }

        /// <summary>
        /// Tries to resolve the service
        /// </summary>
        /// <param name="service">The service.</param>
        /// <returns></returns>
        public object TryResolve(Type service)
        {
            try
            {
                return Resolve(service);
            }
            catch
            {
                return null;
            }
        }
        
        /// <summary>
        /// Tries the resolve.
        /// </summary>
        /// <typeparam name="T">type to resolve</typeparam>
        /// <returns></returns>
        public T TryResolve<T>()
        {
            return (T)TryResolve(typeof(T));
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    //cleanup managed code, dispose of all types loaded from the container that implement IDisposable.
                    var objectsToDispose = _components.Where(x => x.Instance != null).ToList();
                    foreach (ComponentInfo ci in objectsToDispose)
                    {
                        //ensure we don't try and dispose ourselfs!
                        if (ci.Instance != null && !ci.Instance.Equals(this) && ci.Instance.GetType() != typeof(CompactServiceLocator))
                        {
                            var idisposable = ci.Instance as IDisposable;
                            if (idisposable !=null)
                            {
                                idisposable.Dispose();
                            }
                        }
                    }
                }
                _disposed = true;
            }
        }

        private ComponentInfo GetComponentInfo(Type service)
        {
            return _components.FindForService(service) ?? _components.FindForClass(service);
        }

        private ComponentInfo GetComponentInfo(string key)
        {
            return _components.FindKey(key);
        }

        private object ResolveComponent(ComponentInfo ci)
        {
            lock (ci)
            {
                if (!HasComponent(ci.ServiceType))
                {
                    throw new ContainerException("Component not registered " + ci.ServiceType.Name);
                }

                if (ci.IsResolvingDependencies)
                {
                    throw new ContainerException("Circular reference: " + ci.ServiceType.Name);
                }

                switch (ci.Lifestyle)
                {
                    case LifestyleType.Singleton:

                        if (ci.Instance == null)
                        {
                            ci.IsResolvingDependencies = true;
                            ci.Instance = HandleCreateNew(ci.ClassType);
                            ci.IsResolvingDependencies = false;
                            _singletonInstanceCount++;
                        }

                        return ci.Instance;

                    case LifestyleType.Transient:

                        ci.IsResolvingDependencies = true;
                        object result = HandleCreateNew(ci.ClassType);
                        ci.IsResolvingDependencies = false;
                        return result;
                }

                return null;
            }
        }

        private object HandleCreateNew(Type classType)
        {
            IHandler handler = DefaultHandler;

            foreach (KeyValuePair<Type, IHandler> pair in _handlers)
            {
                if (pair.Key.IsAssignableFrom(classType))
                {
                    handler = pair.Value;
                    break;
                }
            }

            return handler.Create(classType);
        }
    }
}